package com.gusto.engine.colfil.neighborhood.storedistance.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.gusto.engine.colfil.Distance;
import com.gusto.engine.colfil.Prediction;
import com.gusto.engine.colfil.Rating;
import com.gusto.engine.colfil.neighborhood.storedistance.DistancePersistenceDAO;
import com.gusto.engine.colfil.neighborhood.storedistance.DistanceProcessor;
import com.gusto.engine.colfil.service.CollaborativeService;

/**
 * <p>This implementation inserts the ratings and updates all the distances 
 * between the rated item and all the items rated by the user,
 * and between the user and all the users having rated the item.</p>
 * 
 * @author amokrane.belloui@gmail.com
 *
 */
public class DistanceProcessorImpl implements DistanceProcessor {
	
	private Logger log = Logger.getLogger(getClass());
	
	// TODO Add a dispatching table containing distances to calculate with : Times added, Last date Added, Priority, ...
	// TODO Triggering the Distance Calculation (Time, Event, ...)
	
	public Map<String, Integer> distances = new HashMap<String, Integer>();
	public Map<String, Integer> getDistances() {
		return distances;
	}
	
	private CollaborativeService collaborativeService;
	public void setCollaborativeService(CollaborativeService collaborativeService) {
		this.collaborativeService = collaborativeService;
	}
	
	private DistancePersistenceDAO distanceDAO;
	public void setDistanceDAO(DistancePersistenceDAO distanceDAO) {
		this.distanceDAO = distanceDAO;
	}
	
	public void insertRating(long userId, long itemId, Rating rating, boolean checkExistence) {
		log.info("insert into Matrix : " + userId + ", " + itemId + " => " + rating.getValue());
		collaborativeService.insertRating(userId, itemId, rating, checkExistence);
		
		log.info("Items Rated by User " + userId);
		List<Rating> evalsU = collaborativeService.getItemsRatedByUser(userId, false);
		for (Rating e : evalsU) {
			log.info("Recalculate distance between items : " + itemId + "-" + e.getItemId());
			String distId = "";
			if (userId > e.getUserId()) {
				distId = "u"+e.getItemId()+"-"+itemId;
			}
			else {
				distId = "i"+itemId+"-"+e.getItemId();
			}
			if (distances.get(distId) == null ) {
				distances.put(distId, 1);
			}
			else {
				distances.put(distId, distances.get(distId) + 1);
			}
			
			Distance itemDist = collaborativeService.calculateItemDistance(itemId, e.getItemId());
			if (itemDist != null) {
				if (itemId != e.getItemId()) {
					log.debug("ITEM" + itemDist);
					if (!itemDist.getDistance().equals(Double.NaN)) {
						distanceDAO.updateItemDistance(itemId, e.getItemId(), 
								new Distance(itemDist.getId1(), itemDist.getId2(), itemDist.getCount(), itemDist.getDistance())
						);
					}
				}
			}
			
		}
		
		log.info("Users having Rated Item " + itemId);
		List<Rating> evalsI = collaborativeService.getUsersHavingRatedItem(itemId, false);
		for (Rating e : evalsI) {
			log.info("Recalculate distance between users : " + userId + "-" + e.getUserId());
			String distId = "";
			if (userId > e.getUserId()) {
				distId = "u"+e.getUserId()+"-"+userId;
			}
			else {
				distId = "u"+userId+"-"+e.getUserId();
			}
			if (distances.get(distId) == null ) {
				distances.put(distId, 1);
			}
			else {
				distances.put(distId, distances.get(distId) + 1);
			}
			
			Distance userDist = collaborativeService.calculateUserDistance(userId, e.getUserId());
			if (userDist != null) {
				if (userId != e.getUserId()) {
					log.debug("USER" + userDist);
					if (!userDist.getDistance().equals(Double.NaN)) {
						distanceDAO.updateUserDistance(userId, e.getUserId(), 
								new Distance(userDist.getId1(), userDist.getId2(), userDist.getCount(), userDist.getDistance())
						);
					}
				}
			}
			
		}
		
	}
	
	public void insertPrediction(long userId, long itemId, Prediction prediction) {
		log.info("insert Prediction into Matrix : " + userId + ", " + itemId + " => " + prediction.getValue());
		collaborativeService.insertPrediction(userId, itemId, prediction);
	}
	
	public void flushDistances() {
		Map<String, Integer> distances = this.distances;
		int wonUser = 0;
		int wonItem = 0;
		int all = 0;
		int calc = 0;
		for (String key : distances.keySet()) {
			log.debug(key + " " + distances.get(key));
			if (key.startsWith("u") && distances.get(key) > 1) {
				wonUser += distances.get(key) - 1;
			}
			if (key.startsWith("i") && distances.get(key) > 1) {
				wonItem += distances.get(key) - 1;
			}
			all += distances.get(key);
			calc += 1;
		}
		log.debug("Users won: " + wonUser + ", Items won: " + wonItem);
		log.info("Distances: " + calc + " to be calculated rather than " + all + " " + (calc * 100 / all) + "%");
	}
	
}
